package simplex

/**
 * Algoritmo del metodo Simplex.
 * 
 * @author age, joaquinmonleon
 *
 */
class Simplex (signo: Int, maximizar: Boolean, varObj: String, objetivo: Array[Racional], termIndep: Array[Racional], numVariables: Int, numRestricciones: Int, datos: Array[Array[Racional]]) {
  
  var debug = true
  
  val numVarHol = termIndep.length
  var B = Matrix.getBMatrix(numVarHol)
  
  var A = new Matrix(datos)
  A = Matrix.mUmHoriz(A, B) // A unida con B

  // Comprobar Max Min
  var cambioObjetivo = false
  var cambioSingo: Integer = 1
  var cambioMaximizar: Boolean = false
  var mObj: Matrix = null
  if (maximizar) {
    cambioObjetivo = true
    cambioSingo = signo * -1
    cambioMaximizar = !maximizar
    mObj = Matrix.createOneRowMatrix(objetivo.clone())
    mObj.mulRowScalar(0, new Racional(-1, 1))
  } else {
    mObj = Matrix.createOneRowMatrix(objetivo)
  }
  val Cb = Matrix.mUmHoriz(
      mObj, 
      Matrix.getEmptyMatrix(1, numVarHol)
      )
  
  var X = Matrix.mUmHoriz(
      Matrix.getEmptyMatrix(1, objetivo.length),
      Matrix.createOneRowMatrix(termIndep)
      )
  
  var Base: Array[Integer] = new Array (numVarHol)
  for (i <- 0 until numVarHol) {
    Base(i) = i+numVariables
  }
    
  if (debug) {
    println ("--- Datos del problema ---");
    println ("Variables = "+numVariables)
    println ("Restricciones = "+numRestricciones)
    println ("Var. Holg = "+numVarHol)
    var fosigno = if (signo < 0) "- " else ""
    var fomaxmin = if (maximizar) "Max " else "Min "
    println ("F. Objetivo = "+ fosigno + fomaxmin + objetivo.deepToString())
    if (cambioObjetivo) {
      fosigno = if (cambioSingo < 0) "- " else ""
      fomaxmin = if (cambioMaximizar) "Max " else "Min "
      println ("F. Objetivo recalculada a = "+ fosigno + fomaxmin + mObj) 
    }
    println("Variable Objetivo = "+varObj)
    println ("Term. Indeps. = "+termIndep.deepToString())
    println ("A =\n"+A)
    println ("Cb =\n"+Cb)
    println ("X =\n"+X)
    println ("Base = "+Base.deepToString())
	println()
  }
  
  def resuelve(): Matrix = {
    
    println ("--- Inicio del algoritmo ---");
	println()
    
    var tenemosOptimo: Boolean = false
    var noAcotado: Boolean = false
    var it = 1
    
    while (!tenemosOptimo && !noAcotado) {
	    var baseCb = Matrix.createOneRowMatrix(Cb.getOneRowArray(Base))
	    var baseX = Matrix.createOneRowMatrix(X.getOneRowArray(Base))
	    
	    // Calcular zj - cj y comprobar si hay optimo
	    tenemosOptimo = true
	    var zc: Array[Racional] = new Array (A.nCols)
	    for (i <- 0 until A.nCols) {
	      var colA = A.getColMatrix(i)
	      var z = Matrix.mXm(baseCb, colA)
	      zc(i) = z.getValue() - Cb.getColValue(i);
	      
	      // Condicion de finalizacion anulada si es > 0
	      if (zc(i) > 0)
	        tenemosOptimo = false
	    }

	    println("---------- Traza "+it+" ----------")
	    println ("Base = "+Base.deepToString())
	    println("A = \n"+A)
	    println("zc = "+zc.deepToString())
	    println("d' = "+baseX)
	    println("Cb = "+baseCb)
	    println()
	    it += 1
	    
	    // Finalizacion
	    if (!tenemosOptimo) {
	    
		    // Obtener k (o indice que entra a la base) como el indice del maximo de los positivos
		    var kEntra = -1
		    var max:Racional = -1.0
		    for (i <- 0 until zc.length) {
		      if (zc(i) > 0 && zc(i) > max) {
		        max = zc(i)
		        kEntra = i
		      }
		    }
		    
		    var yk = A.getCol(kEntra)
		    
		    // Condicion de finalizacion de no acotado
		    // ¿ Aki <= 0 Vi ? Si todos los elementos de Ak son <= 0
		    noAcotado = (yk.slice(0,A.nRows).filter(_>0.0).length == 0)
		
		    // Finalizacion
		    if (!noAcotado) {
		
			    // Indice en la Base del que sale
			    var iSale = -1
			    //var min:Racional = new Racional(10000000,1);// 
			    var first_dy = true  
			    var min:Racional = new Racional(0,1);
			    
			    for (i <- 0 until yk.length) {
			      var dy = baseX.getColValue(i) / yk(i)
			      if (first_dy || (yk(i) > 0 && dy < min)) {
			        min = dy
			        iSale = i
			        first_dy = false
			      }
			    }
			    
			    // Valor en la Base del que sale
			    var kSale =  Base(iSale)
			    // Añadir los valores de solucion de la base a la matriz para reducirlos
			    var ATemp = Matrix.mUmHoriz(A, Matrix.createOneColMatrix(X.getOneRowArray(Base)))
			    reduccionGauss (ATemp, iSale, kEntra)
			    // Obtener los nuevos valores de solucion reducidos
			    var Xvals = ATemp.removeCol(ATemp.nCols-1)
			    // Actualizar Base (cual sale / entra)
			    Base(iSale) = kEntra
			    // Asignar los valores reducidos de solucion con los indices de la nueva base
			    X = Matrix.getEmptyMatrix(X.nRows, X.nCols)
			    for (i <- 0 until Base.length) {
			      X.elements(0)(Base(i)) = Xvals(i)
			    }
			    
			    println("Variables que (sale - entra): ("+(kSale+1)+" - "+(kEntra+1) + ")")
			    println()
			    			    
			    // Nueva A
			    A = ATemp
		    }
	    }
  	}
    
    if (tenemosOptimo) {
      println ("--- Solución óptima ---")
      println ("X* = " + X)
      var solucionOpt: Racional = new Racional(0,1)
      for (i <- 0 until objetivo.length) {
        solucionOpt = solucionOpt + objetivo(i)*X.elements(0)(i)
      }
      solucionOpt = solucionOpt*signo
      println (varObj+"* = " + solucionOpt)
      println()
      X
    } else if (noAcotado) {
      println ("--- El problema es no acotado ---")
	  println()
      return null
    } else {
      println ("--- Fallo desconocido al aplicar el algoritmo ---")      
      println()
      return null
    }
  }
  
  // Reducir la tabla
  def reduccionGauss (m1: Matrix, indFilPiv: Integer, indColPiv: Integer) = {
    //Se dividen los elementos de la fila pivote por el elemento pivote
    var divisor = m1.elements(indFilPiv)(indColPiv);
    m1.elements(indFilPiv) = m1.elements(indFilPiv).map(_ / divisor);
    
    //Convierto a 0 los elementos que coinciden con la columna del elemento pivote
    //de las lineas no pivote. 
    for(i <- 0 until m1.nRows)
      if(i != indFilPiv){
    	var filaPivoteMultiplicada = m1.elements(indFilPiv).map(_ * -(m1.elements(i)(indColPiv)));
    	for(j <- 0 until m1.nCols)
    	  m1.elements(i)(j) = m1.elements(i)(j) + filaPivoteMultiplicada(j);
      }    
  }
}